package br.ufpe.cin.mac.nameService;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import br.ufpe.cin.mac.util.Marshal;

public class NameService {

	private ServerSocketChannel serverChannel;

	private SocketChannel channel;

	private Map<Class<?>, String> binds;

	private static NameService instance = null;

	/**
	 * 
	 */
	private NameService() {
		try {
			this.binds = new HashMap<Class<?>, String>();
			
			
			// Create a non-blocking server socket channel on port 2099
			serverChannel = ServerSocketChannel.open();	        
	        int port = 2099;
	        serverChannel.socket().bind(new InetSocketAddress(port));

			
			System.out.println("NameService running");
			this.run();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static NameService getInstance() {
		if (instance == null) {
			instance = new NameService();
		}
		return instance;
	}

	public void bind(Class<?> iObject, String host, String port) {
		System.out.println("Bind " + iObject.getName() + " to " + host + ":" + port);
		this.binds.put(iObject, host + ":" + port);	
	}

	public void lookup(String service) {
		System.out
				.println("LOOKUP: " + service );
		Set<Class<?>> keys = this.binds.keySet();
		for (Class<?> s : keys) {
			String className = s.getName();
			if (className.equals(service)) {
				String addr = this.binds.get(s);
				sendString(channel,addr);
				ByteBuffer buffer = ByteBuffer.allocate(1);
				//Read Confirmation
				buffer.clear();
				try {
					channel.read(buffer);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} //Must be 1
			
				sendObject(channel, s);
			}
		}
	}

	public void run() {
		while (true) {
			try {
				
				channel = serverChannel.accept();
				System.err.println("Connection accpeted");

				new Thread() {
					public void run() {
						processOP(channel);
						
						try {
							channel.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}.start();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private Object processOP(SocketChannel channel) {	
		Object ret = null;
		try {
			ByteBuffer buffer = ByteBuffer.allocate(5000);
			buffer.clear();
			
			channel.read(buffer);
			buffer.flip();
			byte[] serialized = new byte[buffer.limit()];
			buffer.get(serialized);
			
			final String op = Marshal.recoverString(serialized);
			
			buffer.clear();
			buffer.put((byte)1);
			buffer.flip();
			channel.write(buffer);
			
			String args[] = op.split(":");
			// bind:service:host:port
			if (args[0].equals("lookup")) {
				processLookUp(args[1]);
			} else if (args[0].equals("bind")) {
				ret = processBind(args[1]);				
			}
			return ret;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	private boolean processBind(String op) {
		try {
			//String args[] = op.split(":");
			
			
			ByteBuffer buffer = ByteBuffer.allocate(5000);
			buffer.clear();			
			channel.read(buffer);			
			
			Class<?> iObject = (Class<?>) Marshal.recoverObject(buffer.array());

			this.bind(iObject, channel.socket().getInetAddress().getHostAddress(), op);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	private void processLookUp(String op) {	
		ByteBuffer buffer = ByteBuffer.allocate(1);
		buffer.clear();
		try {
			channel.read(buffer);//Must be 1
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.lookup(op);
	}
	
	private void sendString(SocketChannel channel, String ret) {
		try {
			byte[] serialized = Marshal.serializeString(ret);
			ByteBuffer buffer = ByteBuffer.wrap(serialized);			
			channel.write(buffer);			
		
		} catch (IOException e) {
			e.printStackTrace();
		}
	
	}

	private void sendObject(SocketChannel channel, Object ret) {
		try {
			byte[] serialized = Marshal.serializeObject(ret);
			ByteBuffer buffer = ByteBuffer.wrap(serialized) ;			
			channel.write(buffer);			
		
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		NameService.getInstance();
	}
}
